/**
 * Sampler
 * <p/>
 * User: joakimd
 * Date: 2013-03-12
 * Time: 15:03
 */
package org.dru.masic.synths.flute {
import flash.events.Event;

import org.dru.masic.MasicComponent;
import org.dru.masic.MasicConstants;
import org.dru.masic.MasicOutput;
import org.dru.masic.MasicParameter;
import org.dru.stk.StkUtils;

public class Flute extends MasicComponent {
    private var _out: MasicOutput;
    private var _attack: MasicParameter;
    private var _decay: MasicParameter;
    private var _sustain: MasicParameter;
    private var _release: MasicParameter;
    private var _panning: MasicParameter;
    private var _voices: Vector.<Voice>;
    private var _numVoices: int;
    private var _buf: Vector.<Number>;

    public function Flute() {
        _out = createOutput("out", true);
        //
        _attack = createParameter("attack", 256, 0.0, null, null);
        _decay = createParameter("decay", 256, 0.0, null, null);
        _sustain = createParameter("sustain", 256, 0.0, null, null);
        _release = createParameter("release", 256, 0.0, null, null);
        //
        _panning = createParameter("panning", 128, 0.5, null, null);
        //
        _voices = new Vector.<Voice>(16, true);
        for (var index: int = 0; index < 16; index++) {
            _voices[index] = new Voice();
        }
        _numVoices = 0;
        // TODO: some manager that can route to several targets
        _attack.addEventListener(Event.CHANGE, function (event: Event): void {
            for each (var voice: Voice in _voices) {
                voice._envelope.attack = envRate(_attack.value);
            }
        });
        //
        _decay.addEventListener(Event.CHANGE, function (event: Event): void {
            for each (var voice: Voice in _voices) {
                voice._envelope.decay = envRate(_decay.value);
            }
        });
        //
        _sustain.addEventListener(Event.CHANGE, function (event: Event): void {
            for each (var voice: Voice in _voices) {
                voice._envelope.sustain = _sustain.value;
            }
        });
        //
        _release.addEventListener(Event.CHANGE, function (event: Event): void {
            for each (var voice: Voice in _voices) {
                voice._envelope.release = envRate(_release.value);
            }
        });
        //
        _buf = new Vector.<Number>(MasicConstants.SAMPLE_BUFFER_SIZE, true);
        // TODO: component.notifyAllParametersChange
        _attack.notifyChange();
        _decay.notifyChange();
        _sustain.notifyChange();
        _release.notifyChange();
    }

    private static function envRate(value: Number): Number {
        // instant to 4 seconds, so....
        // value 0 = rate 1
        // value 0.25 = rate 1 / (44100)
        // value 0.5 = rate 1 / (44100 * 2)
        // value 1 = rate 1 / (44100 * 4)
        if (value > 0.0) {
            const max: Number = MasicConstants.SAMPLE_RATE * 4;
            return 1.0 / (max * value);
        }
        return 1.0;
    }

    override public function processNote(note: int, velocity: Number): void {
        var voice: Voice = lookupActive(note);
        if (velocity) {
            if (voice == null) {
                // note on
                if (_numVoices < _voices.length) {
                    // use free voice
                    voice = _voices[_numVoices++];
                }
                else {
                    // reuse oldest voice
                    voice = _voices[0];
                    for (var index: int = 1; index < _voices.length; index++) {
                        _voices[index - 1] = _voices[index];
                    }
                    _voices[_voices.length - 1] = voice;
                    voice.noteOff();
                }
                voice.noteOn(note, velocity);
            }
            else {
                // after touch
                voice._velocity = velocity;
            }
        }
        else if (voice != null) {
            // note off
            voice._envelope.gate = false;
        }
    }

    private function lookupActive(note: int): Voice {
        for (var index: int = _numVoices; --index >= 0;) {
            if (_voices[index]._note == note) {
                return _voices[index];
            }
        }
        return null;
    }

    override public function processSamples(start: int, end: int): void {
        StkUtils.fill(_buf, 0.0, start, end);
        for (var index: int = _numVoices; --index >= 0;) {
            const voice: Voice = _voices[index];
            voice.processSamples(_buf, start, end);
            if (voice.idle) {
                for (var k: int = index + 1; k < _numVoices; k++) {
                    _voices[k - 1] = _voices[k];
                }
                _numVoices--;
                _voices[_numVoices] = voice;
            }
        }
        const pan: Number = _panning.value;
        for (var pos: int = start; pos < end; pos++) {
            _out.left[pos] = _buf[pos] * (1.0 - pan);
            _out.right[pos] = _buf[pos] * pan;
        }
    }
}
}

import org.dru.masic.MasicConstants;
import org.dru.stk.StkDelay;
import org.dru.stk.StkEnvelope;
import org.dru.stk.StkMath;

class Voice {
    internal var _envelope: StkEnvelope;
    internal var _note: int;
    internal var _velocity: Number;
    internal var _delayLine: StkDelay;
    internal var _delayLine2: StkDelay;

    public function Voice() {
        _envelope = new StkEnvelope();
        _envelope.attack = 1.0;
        _envelope.decay = 1.0 / (44100.0 * 2.0);
        _envelope.sustain = 0.15;
        _envelope.release = 1.0 / 5292.0;
        _note = 0;
        _velocity = 0.0;
        _delayLine = new StkDelay();
        _delayLine2 = new StkDelay();
    }

    public function get idle(): Boolean {
        return _envelope.idle;
    }

    public function noteOn(note: int, velocity: Number): void {
        _note = note;
        _velocity = velocity;
        _delayLine.setDuration(MasicConstants.SAMPLE_RATE / (440.0 * Math.pow(2.0, (Number(note) + 24.0 - 69.0) / 12.0)));
        _delayLine2.setDuration(MasicConstants.SAMPLE_RATE / (440.0 * Math.pow(2.0, (Number(note) + 12.0 - 69.0) / 12.0)));
        _envelope.gate = true;
    }

    public function noteOff(): void {
        _envelope.gate = false;
    }

    public function processSamples(dst: Vector.<Number>, start: int, end: int): void {
        const envelope: StkEnvelope = _envelope;
        const velocity: Number = _velocity;
        for (var pos: int = start; pos < end; pos++) {
            const sample: Number = StkMath.random();
            const sample2:Number = _delayLine.next(sample - _delayLine.current() * 0.9);
            const sample3:Number = _delayLine2.next((sample2 - sample) + _delayLine2.current() * 0.9);
            dst[pos] += (sample3 - sample2 * 0.5 + sample) * envelope.next() * velocity * 0.125;
        }
    }
}
